/*
	Copyright © 2019 by The qTox Project Contributors

	This file is part of qTox, a Qt-based graphical interface for Tox.

	qTox is libre software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	qTox is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with qTox.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "bootstrapnodeupdater.h"
#include "util/json.hpp"
#include "util/httplib.hpp"
#include <fstream>
#include<iostream>
#include<string>
#include <list>
#include <vector>
#include <regex>




namespace NodeFields {
	const std::string status_udp{ "status_udp" };
	const std::string status_tcp{ "status_tcp" };
	const std::string ipv4{ "ipv4" };
	const std::string ipv6{ "ipv6" };
	const std::string public_key{ "public_key" };
	const std::string port{ "port" };
	const std::string maintainer{ "maintainer" };
	// TODO(sudden6): make use of this field once we differentiate between TCP nodes, and bootstrap nodes
	const std::string tcp_ports{ "tcp_ports" };
	const std::list<std::string> neededFields{ status_udp, status_tcp, ipv4, ipv6, public_key, port, maintainer };
} // namespace NodeFields

namespace {
	const std::string NodeListAddress{ "nodes.tox.chat:443" };
	const std::string jsonNodeArrayName{ "nodes" };
	const std::string emptyAddress{ "-" };
	const std::regex ToxPkRegEx("(^|\\s)[A-Fa-f0-9]{64}($|\\s)");

	void jsonNodeToDhtServer(const nlohmann::json& node, std::vector<DhtServer>& outList)
	{
		// first check if the node in question has all needed fields
		bool found = true;
		for (const auto& key : NodeFields::neededFields) {
			found &= node.contains(key);
		}

		if (!found) {
			std::cout << "Node is missing required fields." << std::endl;
			return;
		}

		// only use nodes that provide at least UDP connection

		if (!node[NodeFields::status_udp].get<bool>()) {
			return;
		}

		const std::string public_key = node[NodeFields::public_key].get<std::string>();
		const int port = node[NodeFields::port].get<int>();

		// nodes.tox.chat doesn't use empty strings for empty addresses
		std::string ipv6_address = node[NodeFields::ipv6].get<std::string>();
		if (ipv6_address == emptyAddress) {
			ipv6_address = std::string{};
		}

		std::string ipv4_address = node[NodeFields::ipv4].get<std::string>();
		if (ipv4_address == emptyAddress) {
			ipv4_address = std::string{};
		}

		if (ipv4_address.empty() && ipv6_address.empty()) {
			std::cout << "Both ipv4 and ipv4 addresses are empty for" << public_key << std::endl;
		}

		const std::string maintainer = node[NodeFields::maintainer].get<std::string>();

		if (port < 1 || port > std::numeric_limits<uint16_t>::max()) {
			std::cout << "Invalid port in nodes list:" << port << std::endl;
			return;
		}
		const uint16_t port_u16 = static_cast<uint16_t>(port);

		if (!std::regex_match(public_key, ToxPkRegEx)) {
			std::cout << "Invalid public key in nodes list" << public_key << std::endl;
			return;
		}

		DhtServer server;
		server.statusUdp = true;
		server.statusTcp = node[NodeFields::status_udp].get<bool>();
		server.userId = public_key;
		server.port = port_u16;
		server.maintainer = maintainer;
		server.ipv4 = ipv4_address;
		server.ipv6 = ipv6_address;
		outList.push_back(server);
		return;
	}

	std::vector<DhtServer> jsonToNodeList(const  nlohmann::json& nodeList)
	{
		std::vector<DhtServer> result;

		if (!nodeList.is_object()) {
			std::cout << "Bootstrap JSON is missing root object" << std::endl;
			return result;
		}

		//auto rootObj = nodeList.object();
		if (!(nodeList.contains(jsonNodeArrayName) && nodeList[jsonNodeArrayName].is_array())) {
			std::cout << "Bootstrap JSON is missing nodes array" << std::endl;
			return result;
		}
		auto nodes = nodeList[jsonNodeArrayName];

		for (auto it = nodes.begin(); it != nodes.end(); ++it) {
			jsonNodeToDhtServer(*it, result);
		}

		return result;
	}



	std::vector<DhtServer> loadNodesFile(std::string file)
	{
        std::ifstream infile(file.c_str());

        if (!infile.good())		//必须先检测目录是否存在.
		{
            std::string defaultnodes =R"({"nodes":[      {
                        "ipv4": "81.71.161.184",
                        "ipv6": "",
                        "maintainer": "dylan",
                        "port": 33445,
                        "public_key": "85DD42CDF012D2486509A5E58FE44DDB549529595B2664DB9A318F24D89DDB5A",
                        "status_tcp": true,
                        "status_udp": true
                    }]})";
            nlohmann::json j=nlohmann::json::parse(defaultnodes);
			std::cout << "Couldn't read bootstrap nodes" << std::endl;
            return jsonToNodeList(j);
		}
		// read a JSON file
		std::ifstream jsonfile(file.c_str());
		nlohmann::json j;
		jsonfile >> j;

		if (j.is_null()) {
			std::cout << "Failed to parse JSON document" << std::endl;
			return {};
		}

		return jsonToNodeList(j);
	}

	std::string serialize(std::vector<DhtServer> nodes)
	{
		nlohmann::json jsonarray = nlohmann::json::array();
		for (auto& node : nodes) {
			nlohmann::json nodeJson = nlohmann::json({});
			nodeJson[NodeFields::status_udp] = node.statusUdp;
			nodeJson[NodeFields::status_tcp] = node.statusTcp;
			nodeJson[NodeFields::ipv4] = node.ipv4;
			nodeJson[NodeFields::ipv6] = node.ipv6;
			nodeJson[NodeFields::public_key] = node.userId;
			nodeJson[NodeFields::port] = node.port;
			nodeJson[NodeFields::maintainer] = node.maintainer;
			jsonarray.push_back(nodeJson);
		}
		nlohmann::json rootObj = nlohmann::json({});
		rootObj["nodes"] = jsonarray;

		return rootObj.dump();
	}
} // namespace

/**
 * @brief Fetches a list of currently online bootstrap nodes from node.tox.chat
 * @param proxy Proxy to use for the lookup, must outlive this object
 */
BootstrapNodeUpdater::BootstrapNodeUpdater(const std::string& paths)
	:paths{paths }
{}

std::vector<DhtServer> BootstrapNodeUpdater::getBootstrapnodes()
{
    std::ifstream infile(paths.c_str());
    if (! infile.good())		//必须先检测目录是否存在.
	{
		std::cout << "Bootstrap node list not found, creating one with default nodes." << std::endl;
        //requestBootstrapNodes();
	}


	return loadNodesFile(paths);
}

void BootstrapNodeUpdater::requestBootstrapNodes()
{
	//nam.setProxy(proxy);
	//connect(&nam, &QNetworkAccessManager::finished, this, &BootstrapNodeUpdater::onRequestComplete);

	//QNetworkRequest request{NodeListAddress};
	//request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

	//nam.get(request);

	httplib::Client cli(NodeListAddress);
	httplib::Headers headers = {
  { "Content-Type", "application/json" }
	};
	auto res = cli.Get("/json", headers);
	auto err = res.error();
	std::cout << httplib::to_string(err) << std::endl;
	if (res->status == 200) {
		std::cout << res->body << std::endl;
		std::ofstream outfile;
		outfile.open(paths, std::ios::out);
		outfile << res->body;
		outfile.close();
		//auto jsondoc =   nlohmann::json::parse(res->body);
		//if (jsondoc.is_null()) {
		//    // emit availableBootstrapNodes({});
		//}
		//else {
		//    std::vector<DhtServer> result = jsonToNodeList(jsondoc);

		//    //emit  availableBootstrapNodes(result);
		//}
	}
}

